namespace cxxcall
{

static void
cb_receiver (uintptr_t w1, uintptr_t w2, uintptr_t w3, uintptr_t w4,
  cb_fct *env, uintptr_t w5, uintptr_t fp, uintptr_t sp, uintptr_t lr,
  uintptr_t pc, uintptr_t first)
{
  cb_data data;

#ifdef __armhf__

#  define F_(x)   \
     __asm__ __volatile__ ("vstr.32 s" #x ", %0" : "=m" (data.fargs[x]))

  F_(0);
  F_(1);
  F_(2);
  F_(3);
  F_(4);
  F_(5);
  F_(6);
  F_(7);
  F_(8);
  F_(9);
  F_(10);
  F_(11);
  F_(12);
  F_(13);
  F_(14);
  F_(15);

#undef F_
#  define F_(x)   \
     __asm__ __volatile__ ("vstr.64 d" #x ", %0" : "=m" (data.dargs[x]))

  F_(0);
  F_(1);
  F_(2);
  F_(3);
  F_(4);
  F_(5);
  F_(6);
  F_(7);

  data.iarg = &first;
  data.curr = (uintptr_t)(data.iarg + av_base::IARG_NUM);
  data.fanum = 0;
#else
  data.curr = (uintptr_t)&first;
#endif
  data.ianum = 0;
  data.flags = 0;
  data.rtype = type::void_t;
  env->fn (&data, env->arg);

#define SET_REG(Name, Value)   \
  __asm__ __volatile__ ("mov " Name ", %0" : : "r" (Value) : Name)

  switch (data.rtype)
    {
      case type::void_t:
        break;

#define SET_R0(Value)   SET_REG ("r0", ((intptr_t)(Value)))
#define SET_R1(Value)   SET_REG ("r1", ((intptr_t)(Value)))

#define IRET(Typecode, Member)   \
  case type::Typecode:   \
    SET_R0 (data.tmp.Member);   \
    break

        IRET (schar_t, sch);
        IRET (uchar_t, uch);
        IRET (sshort_t, shrt);
        IRET (ushort_t, ushrt);
        IRET (sint_t, ival);
        IRET (uint_t, uival);
        IRET (slong_t, lval);
        IRET (ulong_t, ulval);

        case type::sllong_t:
        case type::ullong_t:
          {
            int r0 = data.tmp.parts.i.u[0], r1 = data.tmp.parts.i.u[1];
            SET_R0 (r0);
            SET_R1 (r1);
            break;
          }

#ifdef __armhf__
        case type::float_t:
          __asm__ __volatile__ ("vldr.32 s0, %0" : : "m" (data.tmp.flt));
          break;

        case type::double_t:
        case type::ldouble_t:
          __asm__ __volatile__ ("vldr.64 d0, %0" : : "m" (data.tmp.dbl));
          break;
#else
        case type::float_t:
          SET_R0 (*(int *)&data.tmp.flt);
          break;

        case type::double_t:
        case type::ldouble_t:
          {
            twoint q = { *(long long *)&data.tmp.dbl };
            int r0 = q.i.u[0], r1 = q.i.u[1];
            SET_R0 (r0);
            SET_R1 (r1);
            break;
          }
#endif

        case type::struct_t:
          if (data.flags & av_base::reg_struct_ret)
            switch (data.rsize)
              {
                case 1:
                  SET_R0 (*(uint8_t *)data.raddr);
                  break;
                case 2:
                  SET_R0 (*(uint16_t *)data.raddr);
                  break;
                case 4:
                  SET_R0 (*(uint32_t *)data.raddr);
                  break;
              }
    }
}

static const size_t TRAMP_LEN = 48;

void cb_data::fill (void *outp, void (*fn) (cb_data *, void *), void *argx)
{
  void *fct = (void *)cb_receiver;
  void **arg = (void **)((char *)outp + TRAMP_LEN);

  const unsigned int ops[] =
    {
      0xE1A0C00D, 0xE92D000F, 0xE92DD800, 0xE24CB014, 0xE24DD008,
      0xE59FC00C, 0xE58DC000, 0xE1A0E00F, 0xE59FF004, 0xE91BA800
    };

  for (int i = 0; i < 10; ++i)
    ((uintptr_t *)outp)[i] = ops[i];

  outp = (char *)outp + sizeof (ops);
  ((uintptr_t *)outp)[0] = (uintptr_t)arg;
  ((uintptr_t *)outp)[1] = (uintptr_t)fct;

  arg[0] = (void *)fn, arg[1] = argx;
}

} // namespace cxxcall
